/*
Copyright (C) 2014  Johan Degraeve

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/gpl.txt>.

*/
package helpdiabetesj2me;

/**
 *
 * @author Johan
 */
public class Float {
    
      /** Limit of value */
      private static long maxLimit=Long.MAX_VALUE/100;
      /** ERROR constant */
      /** Johan Degraeve : I hadd to change this constructor, I noticed that when creating ERROR, sometimes LONG.MAXVALUE gives different values */
      final static public Float ERROR=new Float(Long.MAX_VALUE,Long.MAX_VALUE);
      /** Zero constant */
      final static public Float ZERO=new Float();
      /** One constant */
      final static public Float ONE=new Float(1L);
      
      
      /** Mantissa */
      public long m_Val;
      /** Exponent */
      public long m_E;
      //
      /**
       * Create object with zero inside
       */
      public Float()
      {
        m_Val=m_E=0;
      }
      /**
       * Create object and makes initialization with value
       * @param value long - value
       */
      public Float(long value)
      {
        m_Val=value;
        m_E=0;
      }
      /**
       * Create object and makes initialization both mantissa and exponent
       * @param value long - mantissa
       * @param e long - exponent
       */
      public Float(long value, long e)
      {
        m_Val=value;
        if(m_Val==0)
          m_E=0;
        else
          m_E=e;
      }
      /**
       * Create object and makes initialization by other Float object
       * @param value Float - source object
       */
      public Float(Float value)
      {
        m_Val=value.m_Val;
        if(m_Val==0)
          m_E=0;
        else
          m_E=value.m_E;
      }
      /**
       * Check value of current Float object is NaN
       * @return boolean - true-if NaN, false-if not
       */
      public boolean isError()
      {
        return (this.m_Val==ERROR.m_Val && this.m_E==ERROR.m_E);
      }
      /** Convert Float object to string */
      public String toString()
      {
        if(isError())
          return "NaN";
        //
        RemoveZero();
        //
        Long l=new Long(m_Val);
        String str=l.toString();
        int len=str.length();
        boolean neg=false;
        if(m_Val<0L)
        {
          neg=true;
          str=str.substring(1, len);
          len--;
        }
        //
        StringBuffer sb=new StringBuffer();
        //
        if(m_E<0L)
        {
          int absE=(int)Math.abs(m_E);
          if(absE<len)
          {
            sb.append(str.substring(0, len-absE));
            sb.append(".");
            sb.append(str.substring(len-absE));
          }
          else
          {
            sb.append(str);
            for(int  i=0; i<(absE-len); i++)
              sb.insert(0, "0");
            sb.insert(0, "0.");
          }
        }
        else
        {
          if(len+m_E>6)
          {
            sb.append(str.charAt(0));
            if(str.length()>1)
            {
              sb.append(".");
              sb.append(str.substring(1));
            }
            else
              sb.append(".0");
            sb.append("E"+(len-1+m_E));
          }
          else
          {
            sb.append(str);
            for(int i=0; i<m_E; i++)
              sb.append("0");
          }
        }
        //
        str=sb.toString();
        sb=null;
        if(neg)
          str="-"+str;
        //
        return str;
      }
      /**
       * Add value of argument to value of current Float object and return as new Float object
       * @param value Float - argument
       * @return Float - current+value
       */
      public Float Add(Float value)
      {
        if(value.Equal(ZERO))
          return new Float(this);
        //
        if (value.isError() | this.isError()) {
            return new Float(Float.ERROR);
        }
        long e1=m_E;
        long e2=value.m_E;
        long v1=m_Val;
        long v2=value.m_Val;
        // E must be equal in both operators
        while (e1 != e2)
        {
          if(e1 > e2)
          {
            if(Math.abs(v1)<maxLimit)
            {
              v1*=10;
              e1--;
            }
            else
            {
              v2/=10;
              e2++;
            }
          }
          else
          if(e1 < e2)
          {
            if(Math.abs(v2)<maxLimit)
            {
              v2*=10;
              e2--;
            }
            else
            {
              v1/=10;
              e1++;
            }
          }
        }
        //
        if( (v1>0 && v2>Long.MAX_VALUE-v1) || (v1<0 && v2<Long.MIN_VALUE-v1) )
        {
          v1/=10; e1++;
          v2/=10; e2++;
        }
        //
        if(v1>0 && v2>Long.MAX_VALUE-v1)
          return new Float(ERROR);
        else
        if(v1<0 && v2<Long.MIN_VALUE-v1)
          return new Float(ERROR);
        //
        return new Float(v1+v2, e1);
      }
      /**
       * Subtract value of argument from value of current Float object and return as new Float object
       * @param value Float - argument
       * @return Float - current-value
       */
      public Float Sub(Float value)
      {
        if(value.Equal(ZERO))
          return new Float(m_Val, m_E);
        return Add(new Float(-value.m_Val, value.m_E));
      }
      /**
       * Divide value of current Float object on argument and return as new Float object
       * @param value Float - argument
       * @return Float - current/value
       */
      public Float Mul(long value)
      {
        return Mul(new Float(value, 0));
      }
      /**
       * Multiply value of current Float object on argument and return as new Float object
       * @param value Float - argument
       * @return Float - current*value
       */
      public Float Mul(Float value)
      {
        if(value.Equal(ZERO) || this.Equal(ZERO))
          return new Float(ZERO);
        if(value.Equal(ONE))
          return new Float(this);
        //
        if (value.isError() | this.isError()) {
            return new Float(Float.ERROR);
        }
        boolean negative1=(m_Val<0);
        if(negative1) m_Val=-m_Val;
        boolean negative2=(value.m_Val<0);
        if(negative2) value.m_Val=-value.m_Val;
        // Check overflow and underflow
        do
        {
          if(value.m_Val>m_Val)
          {
            if(Long.MAX_VALUE/m_Val<value.m_Val)
            {
              value.m_Val/=10;
              value.m_E++;
            }
            else
              break;
          }
          else
          {
            if(Long.MAX_VALUE/value.m_Val<m_Val)
            {
              m_Val/=10;
              m_E++;
            }
            else
              break;
          }
        } while(true);
        //
        if(negative1) m_Val=-m_Val;
        if(negative2) value.m_Val=-value.m_Val;
        //
        long e=m_E+value.m_E;
        long v=m_Val*value.m_Val;
        return new Float(v, e);
      }
      /**
       * Divide value of current Float object on argument and return as new Float object
       * @param value Float - argument
       * @return Float - current/value
       */
      public Float Div(long value)
      {
        return Div(new Float(value, 0));
      }
      /**
       * Divide value of current Float object on argument and return as new Float object
       * @param value Float - argument
       * @return Float - current/value
       */
      public Float Div(Float value)
      {
        if(value.Equal(ONE))
          return new Float(this);
        //
        if (value.isError() | this.isError()) {
            return new Float(Float.ERROR);
        }
        long e1=m_E;
        long e2=value.m_E;
        long v2=value.m_Val;
        if(v2==0L)
          return new Float(ERROR);
        long v1=m_Val;
        if(v1==0L)
          return new Float(ZERO);
        //
        long val=0L;
        while(true)
        {
          val+=(v1/v2);
          v1%=v2;
          if(v1==0L || Math.abs(val)>(Long.MAX_VALUE/10L))
            break;
          if(Math.abs(v1)>(Long.MAX_VALUE/10L))
          {
            v2/=10L;
            e2++;
          }
          else
          {
            v1*=10L;
            e1--;
          }
          val*=10L;
        }
        //
        Float f=new Float(val, e1-e2);
        f.RemoveZero();
        return f;
      }
      public void RemoveZero()
      {
        if(m_Val==0)
          return;
        while ( m_Val%10 == 0 )
        {
         m_Val/=10;
         m_E++;
        }
      }
      
      /**
       * Returns a new Float object initialized to the value represented by the specified String
       * @param str String - the string to be parsed
       * @param radix int - basement of number
       * @return Float - the Float object represented by the string argument
       */
      static public Float parse(String str, int radix)
      {
        // Abs
        boolean neg=false;
        if(str.charAt(0)=='-')
        {
          str=str.substring(1);
          neg=true;
        }
        //
        int pos=str.indexOf(".");
        long exp=0;
        // Find exponent position
        int pos2=str.indexOf('E');
        if(pos2==-1) pos2=str.indexOf('e');
        //
        if(pos2!=-1)
        {
          String tmp=new String(str.substring(pos2+1));
          exp=Long.parseLong(tmp);
          str=str.substring(0, pos2);
        }
        //
        if(pos!=-1)
        {
          for(int m=pos+1; m<str.length(); m++)
          {
            if(Character.isDigit(str.charAt(m)))
              exp--;
            else
              break;
          }
          str=str.substring(0, pos)+str.substring(pos+1);
          while(str.length()>1 && str.charAt(0)=='0' && str.charAt(1)!='.')
            str=str.substring(1);
        }
        //
        long result=0L;
        int len=str.length();
        //
        StringBuffer sb=new StringBuffer(str);
        while(true)
        {
          // Long value can't have length more than 20
          while(len>20)
          {
            // Very large number for Long
            sb=sb.deleteCharAt(len-1);
            // Compensation of removed zeros
            if(len<pos || pos==-1)
              exp++;
            //
            len--;
          }
          //
          try
          {
            result=Long.parseLong(sb.toString(), radix);
            if(neg)
              result=-result;
            break;
          }
          catch(Exception e)
          {
            // Very large number for Long
            sb=sb.deleteCharAt(len-1);
            // Compensation of removed zeros
            if(len<pos || pos==-1)
              exp++;
            //
            len--;
          }
        }
        sb=null;
        //
        Float newValue=new Float(result, exp);
        newValue.RemoveZero();
        return newValue;
      }
      /**
       * Equal with current Float object?
       * @param x Float - argument
       * @return boolean - true-if equal, false-if not
       */
      public boolean Equal(Float x)
      {
        long e1=m_E;
        long e2=x.m_E;
        long v1=m_Val;
        long v2=x.m_Val;
        //
        if((v1==0 && v2==0) || (v1==v2 && e1==e2))
          return true;
        // Values with exponent differences more than 20 times never could be equal
        /*
        if(Math.abs(e1-e2)>20)
          return false;
        */
        long diff=e1-e2;
        if(diff<-20 || diff>20)
          return false;
        //
        while (e1 != e2)
        {
          if(e1 > e2)
          {
            if(Math.abs(v1)<maxLimit)
            {
              v1*=10;
              e1--;
            }
            else
            {
              v2/=10;
              e2++;
            }
          }
          else
          if(e1 < e2)
          {
            if(Math.abs(v2)<maxLimit)
            {
              v2*=10;
              e2--;
            }
            else
            {
              v1/=10;
              e1++;
            }
          }
        }
        //
        return (v1==v2);
      }
      /**
      * Convert Float object to long number
      * @return long - number
      */
      public long toLong()
      {
        long tmpE=m_E;
        long tmpVal=m_Val;
        //
        while(tmpE!=0)
        {
          if(tmpE<0)
          {
            tmpVal/=10;
            tmpE++;
          }
          else
          {
            tmpVal*=10;
            tmpE--;
          }
        }
        return tmpVal;
      }
      /**
      * Integer part of Float object
      * @param x Float - source Float object
      * @return Float - result Float object
      */
      static public Float Int(Float x)
      {
        long tmpVal=x.m_Val;
        //
        if(x.m_E<0)
        {
          long coeff=1;
          //
          if(x.m_E>-19)
          {
            for(long i=0; i<-x.m_E; i++)
              coeff*=10;
            tmpVal/=coeff;
            tmpVal*=coeff;
          }
          else
            return Float.ZERO;
        }
        //
        return new Float(tmpVal, x.m_E);
      }
}
